﻿using MongoDB.Driver;
using Sustainalytics.ClientDW.Synchronization.SyncESGData.CorporateDataLogic;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.ESG.Entities;
using Sustainalytics.ESGRatings.BusinessLogic;
using Sustainalytics.Events.DataAccessLayer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace Sustainalytics.ClientDW.Synchronization.SyncESGData.CompanyKeyESGIssueLogic
{
    public class CompanyKeyESGIssueProcessor
    {
        private readonly ComprehensiveEsgOrControversyProfilesQuery _comprehensiveEsgOrControversyProfiles;
        private readonly IMongoCollection<SubPeerGroupKeyEsgIssue> _subPeerGroupKeyEsgIssues;
        private readonly EventsDatabase _eventsDatabase;
        private readonly ILogger _logger;

        public CompanyKeyESGIssueProcessor(
            IMongoCollection<SubPeerGroupKeyEsgIssue> subPeerGroupKeyEsgIssues, 
            ComprehensiveEsgOrControversyProfilesQuery comprehensiveEsgOrControversyProfiles, 
            EventsDatabase eventsDatabase, 
            ILogger logger)
        {
            _comprehensiveEsgOrControversyProfiles = comprehensiveEsgOrControversyProfiles;
            _eventsDatabase = eventsDatabase;
            _subPeerGroupKeyEsgIssues = subPeerGroupKeyEsgIssues;
            _logger = logger;
        }

        private Dictionary<Guid, Dictionary<string, int>> GetEventIndicatorsPerCompany(List<Guid> commpaniesIds)
        {
            var eventIndicators = new List<Tuple<Guid, string, int>>();

            var stopWatch = Stopwatch.StartNew();

            using (var eventIndicatorsReader = _eventsDatabase.EventIndicators(commpaniesIds))
            {
                while (eventIndicatorsReader.Read())
                {
                    eventIndicators.Add(Tuple.Create((Guid)eventIndicatorsReader[EventIndicatorSqlFields.CompanyId],
                                                        (string)eventIndicatorsReader[EventIndicatorSqlFields.EventIndicatorName],
                                                        (int)eventIndicatorsReader[EventIndicatorSqlFields.AnswerCategoryId]));
                }
            }

            _logger.Log($"{eventIndicators.Count} event indicators retrieved for " + 
                        $"{new StackFrame(1).GetMethod().Name} in " + 
                        $"{typeof(CompanyKeyESGIssueProcessor).Name} (duration {stopWatch.Elapsed})");

            return eventIndicators.GroupBy(ei => ei.Item1)
                                    .ToDictionary(eig => eig.Key, eig => eig.ToDictionary(ei => ei.Item2.Trim(), ei => ei.Item3, StringComparer.InvariantCultureIgnoreCase));
        }

        public void AddDescriptionToCompanyKeyEsgIssues(IList<CompanyKeyESGIssue> companyKeyEsgIssues)
        {
            var subPeerGroupKeyEsgIssuesDictionary = new Dictionary<int, string>();
            _subPeerGroupKeyEsgIssues.Find(Builders<SubPeerGroupKeyEsgIssue>.Filter.Empty).ToList().ForEach(x =>
            {
                if (!subPeerGroupKeyEsgIssuesDictionary.ContainsKey(x.ExternalId))
                    subPeerGroupKeyEsgIssuesDictionary.Add(x.ExternalId, x.Description);
            });

            Parallel.ForEach(companyKeyEsgIssues, cKEI =>
            {
                cKEI.KeyESGIssues.ForEach(kei =>
                {
                    kei.Description = subPeerGroupKeyEsgIssuesDictionary[kei.ExternalId];
                });
            });
        }

        public void AddEventIndicatorDetails(IList<CompanyKeyESGIssue> companyKeyEsgIssues)
        {
            var eligibleCompanies = _comprehensiveEsgOrControversyProfiles.GetAsync().Result;

            _logger.Log($"{eligibleCompanies.Count} eligible companies retrieved for aggregation purposes for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

            var peerGroupSiblings = eligibleCompanies.GroupBy(c => c.Value).ToDictionary(cg => cg.Key, cg => cg.Select(c => c.Key).ToList());

            var eventIndicatorsPerCompany = GetEventIndicatorsPerCompany(eligibleCompanies.Keys.ToList());

            var eligibleCompanyKeyEsgIssues = companyKeyEsgIssues.Where(ckei => eventIndicatorsPerCompany.ContainsKey(ckei.Id) &&
                                                                                ckei.KeyESGIssues != null &&
                                                                                ckei.KeyESGIssues.Any(kei => kei.KeyEsgIssueIndicators != null &&
                                                                                                                kei.KeyEsgIssueIndicators.Any(keii => keii.Type == KeyEsgIssueIndicatorType.EventIndicator)));

            _logger.Log($"{companyKeyEsgIssues.Count} CompanyKeyESGIssues received for processing for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");
            _logger.Log($"{eligibleCompanyKeyEsgIssues.Count()} CompanyKeyESGIssues left for processing for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

            Parallel.ForEach(eligibleCompanyKeyEsgIssues, companyKeyEsgIssue =>
            {
                string companyPeerGroup;
                if (!eligibleCompanies.TryGetValue(companyKeyEsgIssue.Id, out companyPeerGroup))
                {
                    _logger.Log($"Company '{companyKeyEsgIssue.CompanyName}' with ID '{companyKeyEsgIssue.Id}' was not found in the local corporate data store for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

                    return;
                }

                var aggregationPool = peerGroupSiblings[companyPeerGroup];
                Dictionary<string, int> siblingCompanyEventIndicators;

                var companyEventIndicators = eventIndicatorsPerCompany[companyKeyEsgIssue.Id];

                foreach (var keyEsgIssue in companyKeyEsgIssue.KeyESGIssues)
                {
                    if (keyEsgIssue.KeyEsgIssueIndicators == null)
                    {
                        _logger.Log($"No KeyEsgIssueIndicators found for company '{companyKeyEsgIssue.CompanyName}' and key ESG issue '{keyEsgIssue.Name}' at " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

                        continue;
                    }

                    foreach (var keyEsgIssueIndicator in keyEsgIssue.KeyEsgIssueIndicators)
                    {
                        if (keyEsgIssueIndicator.Type != KeyEsgIssueIndicatorType.EventIndicator)
                        {
                            continue;// as only event indicators need to be augmented with data
                        }

                        int eventIndicatorAnswerCategory;
                        if (!companyEventIndicators.TryGetValue(keyEsgIssueIndicator.Name.Trim(), out eventIndicatorAnswerCategory))
                        {
                            _logger.Log($"No event indicator having the name '{keyEsgIssueIndicator.Name}' exists at company '{companyKeyEsgIssue.CompanyName}' for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

                            continue;
                        }

                        keyEsgIssueIndicator.AnswerCategory = "Category " + eventIndicatorAnswerCategory;

                        var materialBestPracticeGap = eventIndicatorAnswerCategory < 3
                                ? KeyEsgIssueIndicatorMaterialBestPracticeGap.Green
                                : eventIndicatorAnswerCategory > 3
                                    ? KeyEsgIssueIndicatorMaterialBestPracticeGap.Red
                                    : KeyEsgIssueIndicatorMaterialBestPracticeGap.Yellow;

                        keyEsgIssueIndicator.MaterialBestPracticeGap = (int)materialBestPracticeGap;

                        var siblingAnswerCategories = new List<int>();

                        foreach (var siblingCompanyId in aggregationPool)
                        {
                            if (!eventIndicatorsPerCompany.TryGetValue(siblingCompanyId, out siblingCompanyEventIndicators))
                            {
                                _logger.Log($"The sibling company '{siblingCompanyId}' for company '{companyKeyEsgIssue.CompanyName}' does not have event indicators at " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

                                continue;
                            }

                            if (!siblingCompanyEventIndicators.TryGetValue(keyEsgIssueIndicator.Name, out eventIndicatorAnswerCategory))
                            {
                                _logger.Log($"No event indicator having the name '{keyEsgIssueIndicator.Name}' exists at the sibling company '{siblingCompanyId}' for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");

                                continue;
                            }

                            siblingAnswerCategories.Add(eventIndicatorAnswerCategory);
                        }

                        if (siblingAnswerCategories.Count > 0)
                        {
                            keyEsgIssueIndicator.Average = "Category " + Convert.ToInt32(siblingAnswerCategories.Average());

                            var leadingPracticeAverageRange = (int)Math.Round(siblingAnswerCategories.Count * 0.25, MidpointRounding.AwayFromZero);
                            var leadingPracticeAverage = leadingPracticeAverageRange == 0 ? 0
                                                            : Convert.ToInt32(siblingAnswerCategories.OrderBy(ac => ac).Take(leadingPracticeAverageRange).Average());

                            keyEsgIssueIndicator.BestPractice = "Category " + leadingPracticeAverage;
                        }
                        else
                        {
                            _logger.Log($"No sibling event indicators collected for company '{companyKeyEsgIssue.CompanyName}' and event indicator '{keyEsgIssueIndicator.Name}' for " + $"{new StackFrame(1).GetMethod().Name} in " + $"{typeof(CompanyKeyESGIssueProcessor).Name} ");
                        }
                    }
                }
            });
        }
    }
}
